package com.google.code.struts2jruby.ognl;

import java.beans.IntrospectionException;
import java.util.Iterator;
import java.util.Map;

import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.OgnlRuntime;

import org.jruby.runtime.builtin.IRubyObject;

import com.opensymphony.xwork2.XWorkException;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

public class JRubyCompoundRootAccessor extends CompoundRootAccessor {
    private final static Logger LOG = LoggerFactory.getLogger(JRubyCompoundRootAccessor.class);
	protected RubyObjectPropertyAccessor jrubyPropertyAccessor = new RubyObjectPropertyAccessor();

    public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException {
        CompoundRoot root = (CompoundRoot) target;
        OgnlContext ognlContext = (OgnlContext) context;

        for (Iterator iterator = root.iterator(); iterator.hasNext();) {
            Object o = iterator.next();

            if (o == null) {
                continue;
            }

            try {
				if (o instanceof IRubyObject) {
					IRubyObject rubyObject = (IRubyObject) o;
					Map vars = rubyObject.getInstanceVariables();
					if (vars.containsKey("@" + (String) name)) {
						// TODO : hack to get around the fact that ruby isn't typed
						if (value instanceof String[] && ((String[]) value).length > 0) {
							value = ((String[]) value)[0];
						}
						jrubyPropertyAccessor.setProperty(context, rubyObject, name, value);
						return;
					}
				} else if (OgnlRuntime.hasSetProperty(ognlContext, o, name)) {
                    OgnlRuntime.setProperty(ognlContext, o, name, value);

                    return;
                } else if (o instanceof Map) {
                    Map map = (Map) o;
                    map.put(name, value);
                    return;
                }
//            } catch (OgnlException e) {
//                if (e.getReason() != null) {
//                    final String msg = "Caught an Ognl exception while setting property " + name;
//                    log.error(msg, e);
//                    throw new RuntimeException(msg, e.getReason());
//                }
            } catch (IntrospectionException e) {
                // this is OK if this happens, we'll just keep trying the next
            }
        }

        Boolean reportError = (Boolean) context.get(ValueStack.REPORT_ERRORS_ON_NO_PROP);

        final String msg = "No object in the CompoundRoot has a publicly accessible property named '" + name + "' (no setter could be found).";

        if ((reportError != null) && (reportError.booleanValue())) {
            throw new XWorkException(msg);
        } else {
            LOG.debug(msg);
        }
    }

	@Override
	public Object getProperty(Map context, Object target, Object name)
			throws OgnlException {
		CompoundRoot root = (CompoundRoot) target;
		OgnlContext ognlContext = (OgnlContext) context;

		if (name instanceof Integer) {
			Integer index = (Integer) name;

			return root.cutStack(index.intValue());
		} else if (name instanceof String) {
			if ("top".equals(name)) {
				if (root.size() > 0) {
					return root.get(0);
				} else {
					return null;
				}
			}

			for (Iterator iterator = root.iterator(); iterator.hasNext();) {
				Object o = iterator.next();

				if (o == null) {
					continue;
				}

				try {
					if (o instanceof IRubyObject) {
						IRubyObject rubyObject = (IRubyObject) o;
						Map vars = rubyObject.getInstanceVariables();
						if (vars.containsKey("@" + (String) name)) {
							return jrubyPropertyAccessor.getProperty(
									ognlContext, rubyObject, name);
						}
					} else if ((OgnlRuntime
							.hasGetProperty(ognlContext, o, name))
							|| ((o instanceof Map) && ((Map) o)
									.containsKey(name))) {
						return OgnlRuntime.getProperty(ognlContext, o, name);
					}
				} catch (OgnlException e) {
					if (e.getReason() != null) {
						final String msg = "Caught an Ognl exception while getting property "
								+ name;
						throw new XWorkException(msg, e);
					}
				} catch (IntrospectionException e) {
					// this is OK if this happens, we'll just keep trying the
					// next
				}
			}

			return null;
		} else {
			return null;
		}
	}
}
